﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Processing.Structures;
using Processing;

namespace RayTracer
{
    /// <summary>
    /// Stellt ein Blatt im Baum dar.
    /// </summary>
    class RayTracerRecursion : IDisposable
    {
        #region Typen
        /// <summary>
        /// "Struktur" für die Ausgabedaten einer Rekursion.
        /// (Struktur da die Ausgabedaten einzigartig sind.)
        /// </summary>
        public struct Output
        {
            /// <summary>
            /// Gibt an ob die Struktur gefüllt ist.
            /// </summary>
            public bool IsNull;

            /// <summary>
            /// Gibt und setzt die Farben der Strahlen der Rekursion.
            /// </summary>
            public Buffer<Float4> Colors;

            /// <summary>
            /// Gibt und setzt die Länge der Strahlen der Rekursion.
            /// </summary>
            public Buffer<float> Distances;

            /// <summary>
            /// Initialisiert die Struktur als Null mit der angegebenen Anzahl an Elementen je Array.
            /// </summary>
            /// <param name="processor">Der Prozessor.</param>
            /// <param name="numberOfElements">Die Anzahl der Elemente</param>            
            public Output(Processor processor, long numberOfElements)
            {
                this.IsNull = true;
                this.Colors = processor.AllocateBuffer<Float4>(numberOfElements);
                this.Distances = processor.AllocateBuffer<float>(numberOfElements);
            }

            /// <summary>
            /// Initialisiert die Struktur mit dem angegebenen Wert.
            /// </summary>
            /// <param name="colors">Die Ergebnisfarben.</param>
            /// <param name="distances">Die Ergebnisstrahlenlängen.</param>
            public Output(Buffer<Float4> colors, Buffer<float> distances)
            {
                this.IsNull = false;
                this.Colors = colors;
                this.Distances = distances;
            }
        }

        /// <summary>
        /// "Klasse" für die Eingabedaten einer Rekursion.
        /// (Klasse damit bei weiterer Referenzierung keine Daten gedoppelt werden müssen.)
        /// </summary>
        public class Input : IDisposable
        {
            #region Eigenschaften
            /// <summary>
            /// Gibt und setzt die Strahlenpositionen.
            /// </summary>
            public Buffer<Float4> RayPositions;

            /// <summary>
            /// Gibt und setzt die Strahlenrichtungen.
            /// </summary>
            public Buffer<Float4> RayDirections;
            #endregion Eigenschaften

            #region Methoden
            /// <summary>
            /// Instanziiert und initialisiert das Objekt.
            /// </summary>
            /// <param name="rayPosition">Die Positionen der Eingangsstrahlen.</param>
            /// <param name="rayDirection">Die Richtungen der Eingangsstrahlen.</param>
            public Input(Buffer<Float4> rayPosition, Buffer<Float4> rayDirection)
            {
                this.RayPositions = rayPosition;
                this.RayDirections = rayDirection;
            }

            /// <summary>
            /// Instanziiert und initialisiert das Objekt leer.
            /// </summary>
            public Input()
            {
                this.RayPositions = null;
                this.RayDirections = null;
            }

            /// <summary>
            /// Gibt das Objekt für den GarbageCollector frei.
            /// </summary>
            public void Dispose()
            {
                this.RayPositions = null;
                this.RayDirections = null;
            }
            #endregion Methoden
        }

        /// <summary>
        /// Repäsentiert den Status einer Rekursion.
        /// </summary>
        private enum State
        {
            /// <summary>
            /// Bedeutet dass die Rekursion im laufenden Zustand ist.
            /// </summary>
            Working,
            /// <summary>
            /// Bedeutet dass die Rekursion im abgeschlossenen Zustand ist.
            /// </summary>
            Finished,
            /// <summary>
            /// Bedeutet dass die Rekursion im fehlgeschlagenen Zustand ist.
            /// </summary>
            Failed,
            /// <summary>
            /// Bedeutet dass die Rekursion im gestoppten Zustand ist.
            /// </summary>
            Stopped
        }
        #endregion Typen

        #region Attribute
        private RayTracer rayTracer;
        private int depth;
        private RayTracerRecursion rtrReflection;
        private RayTracerRecursion rtrRefraction;
        private Thread thread;
        private State state;
        private Input input;
        private Output output;
        private AutoResetEvent waitHandle;
        #endregion Attribute

        #region Methoden
        /// <summary>
        /// Erstellt ein Raytracer Rekursionsobjekt.
        /// </summary>
        /// <param name="thisRayTracer">Gibt den zu grundeliegenden Raytracer an.</param>
        /// <param name="depth">Gibt die verbleibende Rekursionstiefe an.</param>
        /// <param name="inputData">Die Eingabedaten.</param>
        public RayTracerRecursion(RayTracer thisRayTracer, int depth, Input inputData)
        {
            this.rayTracer = thisRayTracer;
            this.depth = depth;
            this.input = inputData;
            this.thread = new Thread(new ThreadStart(run), rayTracer.MaxThreadMemory);
            this.thread.Name = "RayTracerRekursion r-" + depth.ToString();
            this.waitHandle = new AutoResetEvent(false);
            waitHandle.Reset();
            if (depth > 0) { thread.Start(); }
            else { output = new Output(rayTracer.Processor, inputData.RayDirections.Length); }
        }

        /// <summary>
        /// Laufzeit der Rekursion.
        /// </summary>
        private void run()
        {
            try
            {
                //////////////////////////////////////////////////////////////////////////
                // Schnittpunkte
                #region Schnittpunkte
                // Unsere Ergebnisdaten der Schnittpunktberechnungen
                Buffer<Float16> lastIntersectionMaterials = null;
                Buffer<float> lastIntersectionDistances = null;
                Buffer<Float4> lastIntersectionIntersections = null;
                Buffer<Float4> lastIntersectionNormals = null;

                // Kugeln    
                // Zunächst wird geprüft ob überhaupt Kugeln in der Szene sind.
                if (rayTracer.SceneObjectSpheresMaterials != null &&
                    rayTracer.SceneObjectSpheresPositions != null &&
                    rayTracer.SceneObjectSpheresRadiusses != null &&
                    rayTracer.SceneObjectSpheresRotations != null)
                {
                    Kernels.KernelPrimitiveSphere kps = new Kernels.KernelPrimitiveSphere(rayTracer.Processor);
                    // alte Daten zuweisen
                    kps.Materials = lastIntersectionMaterials;
                    kps.LastDistances = lastIntersectionDistances;
                    kps.Intersections = lastIntersectionIntersections;
                    kps.Normals = lastIntersectionNormals;

                    // Strahlen übergeben
                    kps.RayPositions = input.RayPositions;
                    kps.RayDirections = input.RayDirections;

                    // Objekt übergeben
                    kps.ObjectPositions = rayTracer.SceneObjectSpheresPositions;
                    kps.ObjectRotations = rayTracer.SceneObjectSpheresRotations;
                    kps.ObjectMaterials = rayTracer.SceneObjectSpheresMaterials;
                    kps.ObjectRadius = rayTracer.SceneObjectSpheresRadiusses;
                    kps.Run();

                    // alte Daten mit neuen Daten nach Ausführung überschreiben
                    lastIntersectionMaterials = kps.Materials;
                    lastIntersectionDistances = kps.NewDistances;
                    lastIntersectionIntersections = kps.Intersections;
                    lastIntersectionNormals = kps.Normals;

                    kps.Dispose();
                }
                #endregion Schnittpunkte

                //////////////////////////////////////////////////////////////////////////
                // RaySplit
                #region Strahlenaufteilung
                Kernels.KernelRaySplit krs = new Kernels.KernelRaySplit(rayTracer.Processor);
                krs.RayDirections = input.RayDirections;
                krs.Normals = lastIntersectionNormals;
                krs.Materials = lastIntersectionMaterials;
                if (depth - 1 > 0) krs.Run();
                #endregion Strahlenaufteilung

                RayTracerRecursion.Output resRefl;
                RayTracerRecursion.Output resRefr;

                if (depth - 1 > 0)
                {
                    // Nicht parallel da sonst zu viel Speicher in Anspruch genommen wird.
                    rtrReflection = new RayTracerRecursion(rayTracer, depth - 1, new Input(lastIntersectionIntersections, krs.ReflectionDirections));
                    resRefl = rtrReflection.Finish();
                    rtrRefraction = new RayTracerRecursion(rayTracer, depth - 1, new Input(lastIntersectionIntersections, krs.RefractionDirections));
                    resRefr = rtrRefraction.Finish();
                }
                else
                {
                    resRefl = new Output(rayTracer.Processor, lastIntersectionMaterials.Length);
                    resRefr = resRefl;
                }

                //////////////////////////////////////////////////////////////////////////
                // Lichter (folgen hier da diese nicht für die Folgestrahlen benötigt werden und somit parallel berechnet werden können.)
                #region Lichter
                Buffer<int> lastLightVisibilities = null;
                Buffer<Float4> lastLightDirections = null;

                // Kugeln
                // Zunächst wird geprüft ob überhaupt Kugeln in der Szene sind.
                if (rayTracer.SceneObjectSpheresPositions != null &&
                    rayTracer.SceneObjectSpheresRadiusses != null &&
                    rayTracer.SceneObjectSpheresRotations != null)
                {
                    Kernels.KernelSimpleLightPrimitiveSphere kslps = new Kernels.KernelSimpleLightPrimitiveSphere(rayTracer.Processor);
                    // alte Daten zuweisen
                    kslps.LightsVisibilities = lastLightVisibilities;
                    kslps.LightsDirections = lastLightDirections;

                    // Schnittpunktdaten übergeben
                    kslps.SurfaceIntersections = lastIntersectionIntersections;
                    kslps.SurfaceNormals = lastIntersectionNormals;

                    // Objektdaten übergeben
                    kslps.LightPositions = rayTracer.SceneLightSimpleLightsPositions;
                    kslps.ObjectPositions = rayTracer.SceneObjectSpheresPositions;
                    kslps.ObjectRotations = rayTracer.SceneObjectSpheresRotations;
                    kslps.ObjectRadius = rayTracer.SceneObjectSpheresRadiusses;
                    kslps.Run();

                    // alte Daten mit neuen Daten nach Ausführung überschreiben
                    lastLightVisibilities = kslps.LightsVisibilities;
                    lastLightDirections = kslps.LightsDirections;

                    kslps.Dispose();
                }
                #endregion Lichter

                // Nicht mehr benötigte Buffer löschen
                lastIntersectionIntersections.Dispose();

                //////////////////////////////////////////////////////////////////////////
                // Farbberechnung
                #region Farbberechnung
                Kernels.KernelSumColor ksc = new Kernels.KernelSumColor(rayTracer.Processor);
                ksc.EyeDirections = input.RayDirections;
                ksc.ReflectionColors = (depth - 1 == 0 ? null : resRefl.Colors);
                ksc.ReflectionDirections = (depth - 1 == 0 ? null : krs.ReflectionDirections);
                ksc.ReflectionDistances = (depth - 1 == 0 ? null : resRefl.Distances);
                ksc.RefractionColors = (depth - 1 == 0 ? null : resRefr.Colors);
                ksc.RefractionDirections = (depth - 1 == 0 ? null : krs.RefractionDirections);
                ksc.RefractionDistances = (depth - 1 == 0 ? null : resRefr.Distances);
                ksc.Normals = lastIntersectionNormals;
                ksc.ObjectMaterials = lastIntersectionMaterials;
                ksc.LightColors = rayTracer.SceneLightSimpleLightsColors;
                ksc.LightDirections = lastLightDirections;
                ksc.LightVisibilities = lastLightVisibilities;
                ksc.Run();
                #endregion Farbberechnung

                #region Speicher wieder leer Räumen
                lastIntersectionNormals.Dispose();
                lastIntersectionMaterials.Dispose();
                lastLightDirections.Dispose();
                lastLightVisibilities.Dispose();
                ksc.ReflectionColors.Dispose();
                ksc.ReflectionDirections.Dispose();
                ksc.RefractionColors.Dispose();
                ksc.RefractionDirections.Dispose();
                if (resRefl.Distances == resRefr.Distances) resRefr.Distances.Dispose();
                else
                {
                    resRefl.Distances.Dispose();
                    resRefr.Distances.Dispose();
                }

                krs.Dispose();
                ksc.Dispose();
                #endregion Speicher wieder leer Räumen

                output = new Output(ksc.ResultColors, lastIntersectionDistances);
                state = State.Finished;
            }
            catch (Exception e)
            {
                if (state != State.Stopped && rayTracer.State != RayTracer.RayTracerState.Stopped) General.ConsolePrinter.PrintExceptionStack(new RayTracerException("RaytracerRekursion gestoppt bei verbleibender Rekusionstiefe von " + depth.ToString() + ".", e));
                state = State.Failed;
            }
            waitHandle.Set();
        }

        /// <summary>
        /// Wartet aktiv bis das Blatt fertig ist.
        /// </summary>
        /// <returns>Die Ausgabe.</returns>
        public Output Finish()
        {
            if (depth > 0) waitHandle.WaitOne();
            return output;
        }

        /// <summary>
        /// Gibt das Objekt für den GarbageCollector frei.
        /// </summary>
        public void Dispose()
        {

            rayTracer = null;

            if (rtrReflection != null)
            {
                rtrReflection.Dispose();
                rtrReflection = null;
            }

            if (rtrRefraction != null)
            {
                rtrRefraction.Dispose();
                rtrRefraction = null;
            }
            thread = null;

            if (input != null)
            {
                input.Dispose();
                input = null;
            }
            output = new Output();
            waitHandle = null;
        }
        #endregion Methoden
    }
}
